﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
using QTPlugin;
using QTPlugin.Interop;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Win32;
using Kawanon.Usability;
using Kawanon.Usability.Actions;

namespace Kawanon.QtHoney
{
	public abstract class PluginCustomItem
	{
		public IPluginClient Plugin { get; set; }
		public IPluginServer Server { get; set; }
		public abstract string Name { get; }
		public abstract bool IsSelectiveText { get; }

		internal virtual bool SingleImage { get { return false; } }

		//protected virtual string GetImageName(bool fLarge)
		//{
		//    throw new NotImplementedException();
		//}

		public virtual Image GetImage(bool fLarge)
		{
			//Stream stream = Utility.GetResourceStream(GetImageName(fLarge));
			//return new Bitmap(stream);

			string name;
			if (SingleImage)
			{
				name = GetType().Name;
			}
			else
			{
				name = String.Format("{0}{1}", GetType().Name, (fLarge ? "_large" : "_small"));
			}
			return (Image)Resource.ResourceManager.GetObject(name);
		}

		protected ToolStripItemDisplayStyle ConvertDisplayStyle(QTPlugin.DisplayStyle style)
		{
			switch (style)
			{
				case DisplayStyle.NoLabel:
					return ToolStripItemDisplayStyle.Image;
				case DisplayStyle.SelectiveText:
					return (IsSelectiveText ? ToolStripItemDisplayStyle.ImageAndText : ToolStripItemDisplayStyle.Image);
				case DisplayStyle.ShowTextLabel:
					return ToolStripItemDisplayStyle.ImageAndText;
			}

			Dbg.Abort("ConvertDisplayStyle");
			return ToolStripItemDisplayStyle.None;
		}

		//protected Size GetDefaultButtonSize(bool fLarge)
		//{
		//    return fLarge ? new Size(29, 31) : new Size(22, 24);
		//}

		protected ToolStripButton CreateSimpleButton(bool fLarge)
		{
			ToolStripButton item = new ToolStripButton();
			//item.AutoSize = false;
			//item.Size = GetDefaultButtonSize(fLarge);
			return item;
		}

		public virtual ToolStripItem OnCreateItem(bool fLarge)
		{
			throw new NotImplementedException();
		}

		public virtual ToolStripItem CreateItem(bool fLarge, QTPlugin.DisplayStyle displayStyle)
		{
			ToolStripItem item = OnCreateItem(fLarge);
			ToolStripItemDisplayStyle style = ConvertDisplayStyle(displayStyle);
			item.ImageScaling = ToolStripItemImageScaling.None;
			item.Image = GetImage(fLarge);
			item.Text = Name;
			item.DisplayStyle = style;
			return item;
		}

		protected void Execute(Action action)
		{
			Action.Arguments args = new Action.Arguments();
			args.Plugin = Plugin;
			args.Server = Server;
			
			action.Execute(args);
		}
	}

	public abstract class ActionInvokerItem : PluginCustomItem
	{
		protected Action m_action;

		public override string Name { get { return m_action.Name; } }

		protected ActionInvokerItem(Action action)
		{
			m_action = action;
		}

		public override ToolStripItem OnCreateItem(bool fLarge)
		{
			ToolStripButton item = CreateSimpleButton(fLarge);
			item.MouseUp += new MouseEventHandler(ToolStripButton_MouseUp);
			return item;
		}

		void ToolStripButton_MouseUp(object sender, MouseEventArgs e)
		{
			if (e.Button != MouseButtons.Left)
			{
				return;
			}
			Execute(m_action);
		}
	}

	namespace PluginItems
	{
		public class ShowSystemMenuButton : PluginCustomItem
		{
			public override string Name { get { return "システムメニュー"; } }

			public override bool IsSelectiveText { get { return false; } }

			public override ToolStripItem OnCreateItem(bool fLarge)
			{
				ToolStripButton item = CreateSimpleButton(fLarge);
				item.MouseUp += new MouseEventHandler(ToolStripButton_MouseUp);
				return item;
			}

			void ToolStripButton_MouseUp(object sender, MouseEventArgs e)
			{
				if (e.Button != MouseButtons.Left)
				{
					return;
				}

				Control parent = Control.FromHandle(Server.Handle);
				ContextMenuStrip contextMenu = (ContextMenuStrip)Utility.ReflectGetFieldValue(parent, "contextMenuSys");

				ToolStripItem item = (ToolStripItem)sender;
				Point pt = new Point(item.Bounds.Left, item.Bounds.Bottom);
				contextMenu.Show(item.Owner.PointToScreen(pt));
			}
		}

		public class MergeWindowsButton : ActionInvokerItem
		{
			public override bool IsSelectiveText { get { return false; } }
			internal override bool SingleImage { get { return true; } }

			public MergeWindowsButton()
				: base(new MergeWindows())
			{
			}
		}

		public class ShowGestureSettingsButton : ActionInvokerItem
		{
			public override bool IsSelectiveText { get { return false; } }

			public ShowGestureSettingsButton()
				: base(new ShowGestureSettings())
			{
			}
		}
	}

	[Plugin(PluginType.BackgroundMultiple,
	Author = "kawanon",
	Name = "QTHoney",
	Version = "0.0.2.1",
	Description = "マウスジェスチャといくつかのボタンを追加します。")]
	public class MainPlugin : PluginClient, IBarMultipleCustomItems
	{
		[DllImport("user32.dll", SetLastError = true)]
		static extern int MapWindowPoints(IntPtr hwndFrom, IntPtr hwndTo, ref POINT lpPoints,
			[MarshalAs(UnmanagedType.U4)] int cPoints);

		[DllImport("user32.dll")]
		static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

		const uint S_OK = 0;

		private const uint FCW_STATUS = 0x0001;

		class AlreadyAttachedException : ApplicationException
		{
		}

		class WindowNotFoundException : ApplicationException
		{
			//public IntPtr ViewHandle { get; private set; }

			//public ShellViewDoesNotHaveListViewException(IntPtr hwnd)
			//{
			//    ViewHandle = hwnd;
			//}
		}

		//private DebugHelpers.ConsoleAllocation alloc = new DebugHelpers.ConsoleAllocation();
		private NativeWindow m_nw = null;
		private ToolTip m_tooltip = null;
		private List<PluginCustomItem> m_items = new List<PluginCustomItem>();
		//private int[] m_order = null;

		//static MainPlugin()
		//{
		//    //// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
		//    //AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AppDomain_AssemblyResolve);
		//}

		// なぜか呼ばれない？（AppDomainが異なる？）
		//static Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs args)
		//{
		//    DebugHelpers.Echo("hoge " + args.Name);

		//    Assembly res = null;
		//    string shortAssemblyName = args.Name.Split(',')[0];
		//    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		//    foreach (Assembly asm in assemblies)
		//    {
		//        if (shortAssemblyName == asm.FullName.Split(',')[0])
		//        {
		//            res = asm;
		//            break;
		//        }
		//    }
		//    return res;
		//}

		public MainPlugin()
		{
			//Console.SetOut(new StreamWriter(Stream.Null));
		}

		~MainPlugin()
		{
			//Echo("Destruct");
		}

		public int Count
		{
			get
			{
				return m_items.Count;
			}
		}

		public override bool HasOption
		{
			get
			{
				return true;
			}
		}

		private PluginCustomItem GetItem(int index)
		{
			//if (m_order != null)
			//{
			//    return m_items[m_order[index]];
			//}
			//else
			//{
			//    return m_items[index];
			//}
			return m_items[index];
		}

		public ToolStripItem CreateItem(bool fLarge, QTPlugin.DisplayStyle displayStyle, int index)
		{
			//ToolStripButton item = new ToolStripButton();
			//item.Text = "システムメニュー";
			//item.ImageScaling = ToolStripItemImageScaling.None;
			//item.AutoSize = false;
			//item.Image = GetImage(fLarge, index);
			//item.DisplayStyle = ToolStripItemDisplayStyle.Image;
			//item.Size = fLarge ? new Size(29, 31) : new Size(22, 24);
			//item.MouseDown += new MouseEventHandler(ToolStripDropDownButton_MouseDown);

			PluginCustomItem custom = GetItem(index);
			ToolStripItem item = custom.CreateItem(fLarge, displayStyle);
			return item;
		}

		public void Initialize(int[] order)
		{
			//string s = (order == null) ? "null" : String.Join(",", Array.ConvertAll(order, i => i.ToString()));
			//Dbg.Echo(s);

			if (order == null)
			{
				return;
			}
			List<PluginCustomItem> list = new List<PluginCustomItem>();
			for (int i = 0; i < order.Length; i++)
			{
				if (-1 < order[i] && order[i] < m_items.Count)
				{
					list.Add(m_items[order[i]]);
				}
			}
			List<PluginCustomItem> rest = m_items.FindAll(item => !list.Contains(item));
			list.AddRange(rest);
			m_items = list;
		}

		public string GetName(int index)
		{
			return GetItem(index).Name;
		}

		public Image GetImage(bool fLarge, int index)
		{
			return GetItem(index).GetImage(fLarge);
		}

		public override void OnOption()
		{
			Form form = new GestureForm(m_server);

			Utility.DoModal(m_server, form);

			//DebugForm f2 = new DebugForm();
			//f2.PropertyGrid.SelectedObject = DynamicReflectorFactory.Create(GetBandObject());
			//f2.Show();
		}

		IShellView GetActiveShellView()
		{
			if (m_browser == null)
			{
				Dbg.Abort("m_browser == null");
				return null;
			}
			IShellView view;
			if (m_browser.QueryActiveShellView(out view) == S_OK)
			{
				return view;
			}
			return null;
		}

		private IntPtr GetActiveViewWindow()
		{
			IShellView view = GetActiveShellView();
			if (view == null)
			{
				// ウィンドウがまだ開いてない
				return IntPtr.Zero;
			}

			IntPtr hwnd = IntPtr.Zero;
			if (view.GetWindow(out hwnd) != S_OK)
			{
				Dbg.Abort("GetWindow");
				return IntPtr.Zero;
			}
			return hwnd;
		}

		private bool AttachToListView(IntPtr hwndView)
		{
			IntPtr listview = Utility.FindChildWindow(hwndView, Utility.EqualsClassName("SysListView32"));
			if (listview == IntPtr.Zero)
			{
				//NOTE: IEエンジンなど
				throw new WindowNotFoundException();
			}
			if (NativeWindow.FromHandle(listview) != null)	// すでにNativeWindowにバインドされてないか調べる
			{
				throw new AlreadyAttachedException();
			}

			IntPtr hwndStatusBar = IntPtr.Zero;
			if (m_browser.GetControlWindow(FCW_STATUS, out hwndStatusBar) != S_OK)
			{
				Dbg.Abort("GetControlWindow Failed");
			}

			NListView nw = new NListView();
			nw.Server = m_server;
			nw.StatusBarHandle = hwndStatusBar;
			nw.ShellView = GetActiveShellView();
			nw.AssignHandle(listview);

			m_nw = nw;	// 参照を保持しておく
			return true;
		}

		private void HideBalloonTip()
		{
			if (m_tooltip == null)
			{
				return;
			}
			Control band = Utility.GetBandObject(m_server);
			m_tooltip.Hide(band);
			//Dbg.Play();
		}

		private void ShowBalloonTip(IntPtr hwnd)
		{
			HideBalloonTip();
			if (m_tooltip == null)
			{
				m_tooltip = new ToolTip();
			}
			m_tooltip.IsBalloon = true;
			m_tooltip.ToolTipTitle = "このビューではマウスジェスチャは使えません。";
			m_tooltip.ToolTipIcon = ToolTipIcon.Warning;

			Control band = Utility.GetBandObject(m_server);
			RECT rc = new RECT();
			if (!GetClientRect(hwnd, out rc))
			{
				Dbg.Abort("GetWindowRect Failed");
			}
			POINT pt = new POINT(new Point(rc.bottom / 2, rc.bottom / 2));
			if (MapWindowPoints(hwnd, band.Handle, ref pt, 1) == 0)
			{
				Dbg.Abort("MapWindowPoints Failed");
			}
			try
			{
				//NOTE: window は内部で Control にキャストされるため、ここでは QTTabBar を親に使う
				m_tooltip.Show("Coming soon...", band, pt.x, pt.y, 5 * 1000);
			}
			catch (Exception ex)
			{
				Dbg.Echo(ex);
			}
			//Dbg.Echo(pt.x +" "+ pt.y);
		}

		private bool AttachToWebBrowser(IntPtr hwndView)
		{
			IntPtr hwnd = Utility.FindChildWindow(hwndView, Utility.EqualsClassName("Internet Explorer_Server"));
			if (hwnd == IntPtr.Zero)
			{
				throw new WindowNotFoundException();
			}
			if (NativeWindow.FromHandle(hwnd) != null)	// すでにNativeWindowにバインドされてないか調べる
			{
				throw new AlreadyAttachedException();
			}

			NWebBrowser nw = new NWebBrowser();
			nw.AssignHandle(hwnd);

			m_nw = nw;
			return true;
		}

		private bool AttachView_Internal()
		{
			IntPtr hwndView = GetActiveViewWindow();
			if (hwndView == IntPtr.Zero)
			{
				return false;
			}
			try
			{
				return AttachToListView(hwndView);
			}
			catch (WindowNotFoundException)
			{
				try
				{
					return AttachToWebBrowser(hwndView);
				}
				catch (WindowNotFoundException)
				{
					Dbg.Abort("WebBrowser Not Found");
					return false;
				}
			}
		}

		private bool AttachView()
		{
			HideBalloonTip();

			bool ret = AttachView_Internal();
			if (!(ret && m_nw is NListView))
			{
				IntPtr hwndStatusBar = IntPtr.Zero;
				if (m_browser.GetControlWindow(FCW_STATUS, out hwndStatusBar) != S_OK)
				{
					Dbg.Abort("GetControlWindow Failed");
				}
				if (hwndStatusBar != IntPtr.Zero)
				{
					ShowBalloonTip(hwndStatusBar);
				}
			}
			return ret;
		}

		private bool DetachView()
		{
			if (m_nw != null)
			{
				m_nw.ReleaseHandle();
				m_nw = null;
				return true;
			}
			else
			{
				return false;
			}

			//Play();
		}

		public override void OnMenuItemClick(MenuType menuType, string menuText, ITab tab)
		{
			Utility.ShowForm(m_server, new GestureForm(m_server));
		}

		private void AddItem(PluginCustomItem item)
		{
			item.Plugin = this;
			item.Server = m_server;

			m_items.Add(item);
		}

		protected override void OnOpen()
		{
			//Dbg.Echo(m_server.GetHashCode());

			//if (PluginInformation.Server != null && PluginInformation.Server != m_server)
			//{
			//    // ここで強制終了することがある...
			//    Dbg.Abort("another PluginInformation.Server already exists");
			//}
			//PluginInformation.Server = m_server;

			PluginInformation.Instances.Add(this);

			AddItem(new PluginItems.MergeWindowsButton());
			AddItem(new PluginItems.ShowSystemMenuButton());
			AddItem(new PluginItems.ShowGestureSettingsButton());

			m_server.NavigationComplete += new PluginEventHandler(PluginServer_NavigationComplete);
			GestureSettings.ExpressionsChanged += new EventHandler(GestureSettings_ExpressionsChanged);

			AttachView();

			m_server.RegisterMenu(this, MenuType.Bar, "マウスジェスチャの設定", true);
		}

		void GestureSettings_ExpressionsChanged(object sender, EventArgs e)
		{
			if (DetachView())
			{
				AttachView();
			}
		}

		void PluginServer_NavigationComplete(object sender, PluginEventArgs e)
		{
			try
			{
				if (!AttachView())
				{
					Dbg.Abort("Attach");
				}
			}
			catch (AlreadyAttachedException)
			{
				//NOTE: 新規ウィンドウで新しく QTTabBar バンドを開いた時は、
				//      IShellBrowser のアクティブビューがまだ切り替わっていない
			}
		}

		public override void Close(EndCode endCode)
		{
			if (endCode == EndCode.Hidden)
			{
				Dbg.Trace("EndCode Hidden");
				return;
			}

			DetachView();

			if (!PluginInformation.Instances.Remove(this))
			{
				Dbg.Abort("not registered in PluginInformation.Instances");
			}

			m_server.NavigationComplete -= new PluginEventHandler(PluginServer_NavigationComplete);
			GestureSettings.ExpressionsChanged -= new EventHandler(GestureSettings_ExpressionsChanged);

			//Echo("Close");
		}

		public static void Uninstall()
		{
			if (Utility.IsDebugMode())
			{
				return;
			}
			if (PluginInformation.OpenRegistryKey(false) == null)
			{
				return;
			}

			string text = String.Format("{0} の設定をすべて削除しますか？",
				Assembly.GetExecutingAssembly().ManifestModule.ScopeName);	// Name は<不明>になる
			DialogResult res = MessageBox.Show(text, "確認",
				MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);
			if (res != DialogResult.Yes)
			{
				return;
			}

			string[] ns = typeof(MainPlugin).Namespace.Split('.');
			RegistryKey key = Registry.CurrentUser.OpenSubKey(
				String.Format(@"Software\Quizo\QTTabBar\Plugins\Settings\{0}", ns[0]), true);
			if (key == null)
			{
				return;
			}
			key.DeleteSubKeyTree(ns[1]);
			key.Flush();
			key.Close();
		}

	}

}
